﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DynORM.Interfaces;
using DynORM.Enums;
using DynORM.DAL;
using DynORM.DAL.Interfaces;
using DynORM.ValueObjects;

namespace DynORM.Mappers
{
    public static class RowDescriptorMapper
    {
        public static IDAL ChooseDAL(DBMSTypes rdbmsKind)
        {
            IDAL dal = null;
            if (rdbmsKind == DBMSTypes.MSSQL)
                dal = new DAL_MSSQL();
            else if (rdbmsKind == DBMSTypes.PostgreSQL)
                dal = new DAL_PostgreSQL();
            else if (rdbmsKind == DBMSTypes.MySQL)
                dal = new DAL_MySQL();
            else if (rdbmsKind == DBMSTypes.Oracle)
                dal = new DAL_Oracle();
            else
                throw new Exception("RDBMS type is not supported.");
            return dal;
        }

        private static void DropRemovedFieldsAndRelations(string connectionString, DBMSTypes rdbmsKind, IRowDescriptor rowDescriptor, IRowDescriptor storedRowDesc)
        {
            IDAL dal = ChooseDAL(rdbmsKind);

            foreach (FieldDescriptor sfd in storedRowDesc.FieldDescriptors)
            {
                bool found = false;
                foreach (FieldDescriptor fd in rowDescriptor.FieldDescriptors)
                {
                    if (fd == sfd)
                    {
                        found = true;
                        break;
                    }
                }
                if (found == false) dal.DropColumn(connectionString, rowDescriptor.TableName, sfd);
            }

            foreach (IRelationDescriptor srd in storedRowDesc.RelationDescriptors)
            {
                bool found = false;
                foreach (IRelationDescriptor rd in rowDescriptor.RelationDescriptors)
                {
                    if ((rd.RelationType == RelationType.PrimaryKey && srd.RelationType == RelationType.PrimaryKey && (PrimaryKeyDescriptor)rd == (PrimaryKeyDescriptor)srd)
                        || rd.RelationType == RelationType.ForeignKey && srd.RelationType == RelationType.ForeignKey && (ForeignKeyDescriptor)rd == (ForeignKeyDescriptor)srd)
                    {
                        found = true;
                        break;
                    }
                }
                if (found == false)
                {
                    if (srd.RelationType == RelationType.ForeignKey)
                        dal.DropForeignKeyConstraint(connectionString, (IForeignKeyDescriptor)srd);
                    else if (srd.RelationType == RelationType.PrimaryKey)
                        dal.DropPrimaryKeyConstraint(connectionString, (IPrimaryKeyDescriptor)srd);
                }
            }
        }

        private static void AddCreatedFieldsAndRelations(string connectionString, DBMSTypes rdbmsKind, IRowDescriptor rowDescriptor, IRowDescriptor storedRowDesc)
        {
            IDAL dal = ChooseDAL(rdbmsKind);
            
            foreach (FieldDescriptor fd in rowDescriptor.FieldDescriptors)
            {
                bool found = false;
                foreach (FieldDescriptor sfd in storedRowDesc.FieldDescriptors)
                {
                    if (fd == sfd)
                    {
                        found = true;
                        break;
                    }
                }
                if (found == false) dal.AddColumn(connectionString, rowDescriptor.TableName, fd);
            }

            foreach (IRelationDescriptor rd in rowDescriptor.RelationDescriptors)
            {
                bool found = false;
                foreach (IRelationDescriptor srd in storedRowDesc.RelationDescriptors)
                {
                    if ((rd.RelationType == RelationType.PrimaryKey && srd.RelationType == RelationType.PrimaryKey && (PrimaryKeyDescriptor)rd == (PrimaryKeyDescriptor)srd)
                        || rd.RelationType == RelationType.ForeignKey && srd.RelationType == RelationType.ForeignKey && (ForeignKeyDescriptor)rd == (ForeignKeyDescriptor)srd)
                    {
                        found = true;
                        break;
                    }
                }
                if (found == false)
                {
                    if (rd.RelationType == RelationType.ForeignKey)
                        dal.AddForeignKeyConstraint(connectionString, (IForeignKeyDescriptor)rd);
                    else if (rd.RelationType == RelationType.PrimaryKey)
                        dal.AddPrimaryKeyConstraint(connectionString, (IPrimaryKeyDescriptor)rd, false);
                }
            }
        }

        public static void Store(string connectionString, DBMSTypes rdbmsKind, IRowDescriptor rowDescriptor)
        {
            CheckIntegrityResult cir = rowDescriptor.CheckStructureIntegrity(false);
            if (cir.Result == false)
                throw new Exception(cir.Reason);

            IDAL dal = ChooseDAL(rdbmsKind);

            if (dal.CheckTable(connectionString, rowDescriptor.TableName))
            {
                IRowDescriptor storedRowDesc = dal.SelectTableStructure(connectionString, rowDescriptor.TableName);
                DropRemovedFieldsAndRelations(connectionString, rdbmsKind, rowDescriptor, storedRowDesc);
                AddCreatedFieldsAndRelations(connectionString, rdbmsKind, rowDescriptor, storedRowDesc);
            }
            else
            {
                dal.CreateTable(connectionString, rowDescriptor);
            }
        }

        public static IRowDescriptor Retrieve(string connectionString, DBMSTypes rdbmsKind, string tableName)
        {
            IDAL dal = ChooseDAL(rdbmsKind);

            IRowDescriptor rowDescriptor = dal.SelectTableStructure(connectionString, tableName);
            if (rowDescriptor == null) return null;

            CheckIntegrityResult cir = rowDescriptor.CheckStructureIntegrity(false);
            if (cir.Result == false)
                throw new Exception(cir.Reason);
            return rowDescriptor;
        }

        public static IEnumerable<IRowDescriptor> Retrieve(string connectionString, DBMSTypes rdbmsKind)
        {
            IDAL dal = ChooseDAL(rdbmsKind);

            IEnumerable<IRowDescriptor> rowDescriptors = dal.GetAllTablesStructure(connectionString);
            foreach (IRowDescriptor rd in rowDescriptors)
            {
                CheckIntegrityResult cir = rd.CheckStructureIntegrity(false);
                if (cir.Result == false)
                    throw new Exception(cir.Reason);
            }
            return rowDescriptors;
        }

        public static void Drop(string connectionString, DBMSTypes rdbmsKind, string tableName)
        {
            IDAL dal = ChooseDAL(rdbmsKind);
            dal.DropTable(connectionString, tableName);
        }

        public static bool IsExistsInDB(IRowDescriptor rowDescriptor, string connectionString, DBMSTypes rdbmsKind)
        {
            return IsExistsInDB(rowDescriptor.TableName, connectionString, rdbmsKind);
        }

        public static bool IsExistsInDB(string tableName, string connectionString, DBMSTypes rdbmsKind)
        { 
            IRowDescriptor storedRD = Retrieve(connectionString, rdbmsKind, tableName);
            if (storedRD != null)
                return true;
            else
                return false;
        }
    }
}
